use crate::Point;
/// c++ binds for sm2p
use crate::U256;

extern "C" {
    fn vli_mod_inv(result: *mut u64, x: *const u64, prime: *const u64);
    fn vli_sm2p_mod(result: *mut u64, y: *const u64, carry: bool);
    fn vli_sm2p_reduction(result: *mut u64, y: *const u64);
    fn vli_sm2p_mod_mult(result: *mut u64, x: *const u64, y: *const u64);
    fn vli_sm2p_sqr(result: *mut u64, x: *const u64);
    fn mont_sm2_mod_mult_n(result: *mut u64, x: *const u64, y: *const u64);
    fn to_mont_sm2_n(result: *mut u64, x: *const u64);
    fn get_curve_params(p: *mut u64, n: *mut u64, b: *mut u64, gx: *mut u64, gy: *mut u64);
    // Point ... forms w/ [u64;4] * 3
    // pt, p, q Point .... [u64;4]*3
    fn point_double_jacobian(pt: *mut u64, p: *const u64);
    fn point_add_jacobian(pt: *mut u64, p: *const u64, q: *const u64);
    fn point_recoverY(y: *mut u64, x: *const u64, is_odd: u64) -> i32;
    fn ecc_verify(
        rp: *const u64,
        sp: *const u64,
        msgp: *const u64,
        pubx: *const u64,
        puby: *const u64,
    ) -> i32;
    fn ecc_recover(
        rp: *const u64,
        sp: *const u64,
        msgp: *const u64,
        v: u64,
        pubx: *mut u64,
        puby: *mut u64,
    ) -> i32;
    fn ecc_sign(rp: *mut u64, sp: *mut u64, msgp: *const u64, privkey: *const u64) -> i32;
}

#[inline]
pub fn c_mod_inverse(x: &U256, prime: &U256) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        vli_mod_inv(res.mut_ptr(), x.ptr(), prime.ptr());
    }
    res
}

#[inline]
pub fn c_sm2p_mod(x: &U256, carry: bool) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        vli_sm2p_mod(res.mut_ptr(), x.ptr(), carry);
    }
    res
}

#[inline]
pub fn c_sm2p_reduction(x: &U256) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        vli_sm2p_reduction(res.mut_ptr(), x.ptr());
    }
    res
}

#[inline]
pub fn c_sm2p_mult(x: &U256, y: &U256) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        vli_sm2p_mod_mult(res.mut_ptr(), x.ptr(), y.ptr());
    }
    res
}

#[inline]
pub fn c_sm2_sqr(x: &U256) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        vli_sm2p_sqr(res.mut_ptr(), x.ptr());
    }
    res
}

#[allow(dead_code)]
#[inline]
fn c_to_mont_sm2n(x: &U256) -> U256 {
    use std::mem::MaybeUninit;
    let res = MaybeUninit::<U256>::uninit();
    unsafe {
        let mut res = res.assume_init();
        to_mont_sm2_n(res.mut_ptr(), x.ptr());
        res
    }
}

#[inline]
pub fn c_sm2_mult_n(x: &U256, y: &U256) -> U256 {
    let mut res = U256::from(0);
    unsafe {
        mont_sm2_mod_mult_n(res.mut_ptr(), x.ptr(), y.ptr());
    }
    res
}

/// return (p, n)  of curve
#[inline]
pub fn c_get_curve_params() -> (U256, U256) {
    let mut p = U256::from(0);
    let mut n = U256::from(0);
    let mut b = U256::from(0);
    let mut gx = U256::from(0);
    let mut gy = U256::from(0);
    unsafe {
        get_curve_params(
            p.mut_ptr(),
            n.mut_ptr(),
            b.mut_ptr(),
            gx.mut_ptr(),
            gy.mut_ptr(),
        );
    }
    (p, n)
}

/// return pt = p  double jacobian forms
#[inline]
pub fn c_point_double_jacobian(p: &Point) -> Point {
    let mut pt: Point = Default::default();
    unsafe {
        point_double_jacobian(pt.mut_ptr(), p.ptr());
    }
    pt
}

/// return pt = p  + q  jacobian forms
#[inline]
pub fn c_point_add_jacobian(p: &Point, q: &Point) -> Point {
    let mut pt: Point = Default::default();
    unsafe {
        point_add_jacobian(pt.mut_ptr(), p.ptr(), q.ptr());
    }
    pt
}

#[inline]
pub fn c_sm2p_recover_y(x: &U256, is_odd: bool) -> Option<U256> {
    let mut r: U256 = Default::default();
    unsafe {
        let ok = point_recoverY(r.mut_ptr(), x.ptr(), is_odd as u64);
        if ok == 0 {
            return None;
        }
    }
    Some(r)
}

#[inline]
pub fn c_sm2p_sign(msg: &U256, privk: &U256) -> (U256, U256, bool) {
    let mut r: U256 = Default::default();
    let mut s: U256 = Default::default();
    let ec_ind: bool;
    unsafe {
        let ret = ecc_sign(r.mut_ptr(), s.mut_ptr(), msg.ptr(), privk.ptr());
        ec_ind = ret != 0;
    }
    (r, s, ec_ind)
}

#[inline]
pub fn c_sm2p_verify(r: &U256, s: &U256, msg: &U256, px: &U256, py: &U256) -> bool {
    let ok = unsafe { ecc_verify(r.ptr(), s.ptr(), msg.ptr(), px.ptr(), py.ptr()) != 0 };
    ok
}

#[inline]
pub fn c_sm2p_recover(r: &U256, s: &U256, msg: &U256, is_odd: bool) -> (U256, U256, bool) {
    let mut px: U256 = Default::default();
    let mut py: U256 = Default::default();
    let ok: bool;
    unsafe {
        let ret = ecc_recover(
            r.ptr(),
            s.ptr(),
            msg.ptr(),
            is_odd as u64,
            px.mut_ptr(),
            py.mut_ptr(),
        );
        ok = ret != 0;
    }
    (px, py, ok)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ecc_const::{SM2_P, SM2_P_RR};
    use crate::test_data::tests::*;
    use crate::U256;
    use measure::Measure;

    #[test]
    fn test_cmod_inverse() {
        let r1 = c_mod_inverse(&dx1, &SM2_P);
        assert!(r1 == x1_inv);
        let r2 = c_mod_inverse(&dx2, &SM2_P);
        assert!(r2 == x2_inv);
    }

    #[test]
    fn test_mult_reduction() {
        let dx1_mo = c_sm2p_mult(&dx1, &SM2_P_RR);
        assert!(dx1_mo == dx1_m);
        let r_dx1 = c_sm2p_reduction(&dx1_m);
        assert!(dx1 == r_dx1);
        let dx1_no = c_to_mont_sm2n(&dx1);
        assert!(dx1_no == dx1_n);
    }

    #[test]
    fn test_c_point_add_jacobian() {
        let p = Point::from(&dx1, &dy1);
        let q = Point::from(&dx2, &dy2);
        let pt = c_point_add_jacobian(&p, &q);
        assert!(*pt.x() == dx3);
        assert!(*pt.y() == dy3);
        assert!(*pt.z() == dz3);
    }

    #[test]
    fn test_c_point_double_jacobian() {
        let p = Point::new(&dx1, &dy1, &U256::from(1));
        let pt = c_point_double_jacobian(&p);
        assert!(*pt.x() == x1x1);
        assert!(*pt.y() == y1y1);
        assert!(*pt.z() == z1z1);
    }

    #[test]
    fn test_c_sm2p_recover_y() {
        if let Some(res) = c_sm2p_recover_y(&d1Gx, d1Gy.is_odd()) {
            assert!(res == d1Gy);
        } else {
            assert!(false, "can't recover1 pointy");
        }
        if let Some(res) = c_sm2p_recover_y(&d2Gx, d2Gy.is_odd()) {
            assert!(res == d2Gy);
        } else {
            assert!(false, "can't recover2 pointy");
        }
    }

    // test_ecc_verfiy collision w/ test_ecc_sign
    #[test]
    fn test_ecc_verify() {
        use crate::curve::CurveSM2;
        use crate::ecc_key::{ec_verify, PrivateKey};
        let curve = CurveSM2::new("sm2p");
        let pk = PrivateKey::new(&curve);
        let (pubx, puby) = pk.public_key();
        let msg = U256::random();
        for _i in 0..10 {
            let (r, s, is_odd) = pk.sign(&curve, &msg);
            assert!(ec_verify(&curve, &r, &s, &pubx, &puby, &msg));
            assert!(c_sm2p_verify(&r, &s, &msg, &pubx, &puby));
            let (px, py, ok) = c_sm2p_recover(&r, &s, &msg, is_odd);
            assert!(ok, "c_sm2p_recover failed");
            assert!(c_sm2p_verify(&r, &s, &msg, &px, &py));
        }
    }

    #[test]
    fn test_ecc_sign() {
        use crate::curve::CurveSM2;
        use crate::ecc_key::{ec_recover, ec_verify, PrivateKey};
        let curve = CurveSM2::new("sm2p");
        let pk = PrivateKey::new(&curve);
        let privk = pk.private_key();
        let (pubx, puby) = pk.public_key();
        let msg = U256::random();
        // c_sm2p_verify not works w/ --release
        // change opt-level = 2 for --release profile
        for _i in 0..10 {
            let (r, s, is_odd) = c_sm2p_sign(&msg, privk);
            assert!(ec_verify(&curve, &r, &s, &pubx, &puby, &msg));
            assert!(c_sm2p_verify(&r, &s, &msg, &pubx, &puby));
            let (px, py, ok) = ec_recover(&curve, &r, &s, &msg, is_odd);
            assert!(ok, "ec_recover failed");
            assert!(c_sm2p_verify(&r, &s, &msg, &px, &py));
            let (_px, _py, ok) = c_sm2p_recover(&r, &s, &msg, is_odd);
            assert!(ok, "c_sm2p_recover failed");
        }
    }

    #[test]
    #[ignore]
    fn bench_cmod_inverse() {
        const N: u32 = 1000_000;
        let mut measure = Measure::start("cmod_inverse bench");
        for _it in 0..N {
            let _r1 = c_mod_inverse(&dx1, &SM2_P);
        }
        measure.stop();
        let ns_ops = measure.as_ns() / (N as u64);
        println!("cmod_inverse cost {} ns per Op", ns_ops);
    }

    #[test]
    #[ignore]
    fn bench_csm2p_reduction() {
        const N: u32 = 100_000;
        let mut measure = Measure::start("csm2p_reduction bench");
        for _it in 0..N {
            let _r1 = c_sm2p_reduction(&dx1_m);
        }
        measure.stop();
        let ns_ops = measure.as_ns() / (N as u64);
        println!("csm2p_reduction cost {} ns per Op", ns_ops);
    }

    #[test]
    #[ignore]
    fn bench_csm2p_mult() {
        const N: u32 = 100_000;
        let mut measure = Measure::start("csm2p_mult bench");
        for _it in 0..N {
            let _r1 = c_sm2p_mult(&dx1, &SM2_P_RR);
        }
        measure.stop();
        let ns_ops = measure.as_ns() / (N as u64);
        println!("csm2p_mult cost {} ns per Op", ns_ops);
    }

    #[test]
    #[ignore]
    fn bench_csm2p_sign() {
        use crate::curve::CurveSM2;
        use crate::ecc_key::PrivateKey;
        let curve = CurveSM2::new("sm2p");
        let pk = PrivateKey::new(&curve);
        let privk = pk.private_key();
        let msg = U256::random();
        const N: u32 = 100_000;
        let mut measure = Measure::start("csm2p_sign bench");
        for _it in 0..N {
            let (_r, _s, _is_odd) = c_sm2p_sign(&msg, privk);
        }
        measure.stop();
        let ns_ops = measure.as_ns() / (N as u64);
        let nops = N as u64 * 1_000_000_000 / measure.as_ns();
        println!("c_sm2p_sign cost {} ns per Op, {} msg/sec", ns_ops, nops);
    }
}
